﻿using System;
using System.Configuration;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Reflection;
using System.Web.Routing;
using System.Web.Configuration; 

//[assembly: PreApplicationStartMethod(typeof(Sharp.PreApplicationStartCode), "Start")] 
namespace Sharp
{
    public static class PreApplicationStartCode
    {

        static public string AssemblyDirectory
        {
            get
            {
                string codeBase = Assembly.GetExecutingAssembly().CodeBase;
                UriBuilder uri = new UriBuilder(codeBase);
                string path = Uri.UnescapeDataString(uri.Path);
                return Path.GetDirectoryName(path);
            }
        }

        private static bool _startWasCalled = false;

        public static void Start()
        {
            if (!_startWasCalled)
            {
                _startWasCalled = true;
                Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(Module));
            }
        }
    }

    public class Module : IHttpModule
    { 

        public void Init (HttpApplication App)
		{

			var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies ().ToList ();
			var loadedPaths = loadedAssemblies.Select (a => a.Location).ToArray ();

			var referencedPaths = Directory.GetFiles (PreApplicationStartCode.AssemblyDirectory, "*.dll");
			var toLoad = referencedPaths.Where (r => !loadedPaths.Contains (r, StringComparer.InvariantCultureIgnoreCase)).ToList ();
			toLoad.ForEach (path => loadedAssemblies.Add (AppDomain.CurrentDomain.Load (AssemblyName.GetAssemblyName (path))));

			foreach (var ass in AppDomain.CurrentDomain.GetAssemblies()) // this was flaking out and not reading unloaded assemblies ... so the above linq is loading all the DLL's in the directory.
                
				try {

					foreach (Type type in ass.GetTypes())
						if (typeof(Handler).IsAssignableFrom (type)) {  
							// Method endpoint
							foreach (var method in type.GetMethods())
								if (method.GetCustomAttributes (typeof(HttpHandler), false).Length == 1) {
									AddRoute (type, method, typeof(MethodEndpoint<>), method);
								}
						}
				} catch (ReflectionTypeLoadException ex) {    
					System.Text.StringBuilder sb = new System.Text.StringBuilder ();
					foreach (var item in ex.LoaderExceptions) {
						sb.AppendLine (item.Message.ToString ());                    
					}

					throw new Exception (sb.ToString ());

				}

			App.Error += new EventHandler (App_Error);
		}

        private static void AddRoute(Type type, object handlerType, Type routetype, object method)
        {
            HttpHandler attr;
            attr = (HttpHandler)((MethodInfo)handlerType).GetCustomAttributes(typeof(HttpHandler), true)[0]; 

            TemplateFile file;
            Object[] AttributesArray;
            AttributesArray = ((MethodInfo)handlerType).GetCustomAttributes(typeof(TemplateFile), true);
            
            if (AttributesArray.Length > 0)
                file = (TemplateFile)AttributesArray[0];


            var values = new RouteValueDictionary();
            values.Add("verb", attr.Verb); 
            values.Add("content-type", attr.Type);
            values.Add("method", method);

            if (AttributesArray.Length > 0)
            {
                file = (TemplateFile)AttributesArray[0];
                values.Add("templatefile", file.Filename);
            }

            var routehandler = (IRouteHandler)Activator.CreateInstance(routetype.MakeGenericType(new Type[] { type }));
            System.Web.Routing.RouteTable.Routes.Add(new System.Web.Routing.Route(attr.Url, values, routehandler)); //new RouteValueDictionary() { { "httpMethod", new HttpMethodConstraint(new string[] { attr.Verb.ToStringValue() })} }, 
        }

        void App_Error(object sender, EventArgs e)
        {
            try
            {
                new Error(sender, e);
            }
            catch (Exception ex)
            {
                ((HttpApplication)sender).Context.Response.Write(ex.Message);
            }
           
        } 

        /// <summary>
        ///  A handler that is a method in a class
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class MethodEndpoint<T> : System.Web.Routing.IRouteHandler where T : IHttpHandler, new()
        {

            public IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
            {
                HttpContext.Current.Items.Add("handler", typeof(T)); //this is only necessary for error handling, find a better way

                T t = new T();
                var httphandler =  (Handler)(object)t; // (Handler)Activator.CreateInstance(typeof(T), new Object[] { });
                httphandler.handler = httphandler;
                httphandler.method = (MethodInfo)requestContext.RouteData.Values["method"];
                
				HttpContext.Current.Items.Add("method", httphandler.method); //in case the handler dies you'll need access to this for debug

                if (requestContext.RouteData.Values["templatefile"] != null)
                    httphandler.template = new Template(new Uri(HttpContext.Current.Server.MapPath(requestContext.RouteData.Values["templatefile"].ToString())));
                return httphandler; 

            }
        }

  		public void Dispose()
        {
            
        } 
 
    }


}
